/**
 * This class will realize database 
 * class Database realized with ArrayList
 */
package it.sevenbits.model;

import it.sevenbits.view.Journal;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;

import org.apache.log4j.Logger;

public class Database {

    private List<String> dBase;
    ResourceBundle bundle = null;
    private static final Logger log4= Logger.getLogger(Database.class);
    private Journal undoLog;
    private String nameDataBase;
    
    /**
     * Constructor
     */
    public Database() {

        dBase = new ArrayList<String>();
        bundle = ResourceBundle.getBundle("db");
        nameDataBase = bundle.getString("db.nameDataBase");
        undoLog = new Journal();
    }

    /**
     * Finds record in database which equals @param record.
     * @param record
     * @return
     */
    private Integer find(String record) {
        
        Integer index = 0;
        try {
            for (String str : dBase) {
                String cleanStr = new String(str);
                cleanStr = cleanStr.replaceAll("\";\"", " ");
                cleanStr = cleanStr.substring(1, cleanStr.length()-2);
                if (cleanStr.equalsIgnoreCase(record)) {
                    return index;
                }
                index++;
            }
        }
        catch (Exception e) {
            log4.error("error in find: ", e);
        }
        return index;
    }
    /**
     * Finds all substrings in database which username equals @param name.
     * 
     * @param name
     * @return
     */
    private ArrayList<Integer> findAll(String name) {

        String record;
        ArrayList<Integer> indexFound = new ArrayList<Integer>();
        int count = 0;
        try {
            for (String str : dBase) {
                record = new String(str);
                if (name.length() > record.length()) {
                    continue;
                }
                record = record.substring(0, name.length() + 4);
                if (record.endsWith("\";\"")) {
                    record = record.substring(1, name.length() + 1);
                    if (record.equalsIgnoreCase(name)) {
                        indexFound.add(count);
                    }
                }

                count++;
            }
        } catch (Exception e) {
            log4.error("error in findAll: ", e);
        }
        return indexFound;
    }
    

    /**
     * Replaces delimiters in database's records for outside using.
     * 
     * @param str
     * @return
     */
    private String replace(String str) {

        try {
            int offset = 0;
            str = str.replaceFirst("\"", "");
            for (int i = 0; i < str.length(); i += offset) {
                int indexDelim = str.indexOf('"', offset + 1);
                int indexSlash = str.indexOf('/');
                int indexSemicolon = str.indexOf(';');
                if (indexDelim == -1) {
                    break;
                }
                if (indexDelim != indexSlash + 1) {
                    str = str.replaceFirst("\"", "");
                } else {
                    str = str.replaceFirst("\"", "\'");
                }
                if (indexSemicolon != indexSlash + 1) {
                    str = str.replaceFirst(";", " ");
                } else {
                    str = str.replaceFirst(";", ",");
                }
                offset = indexDelim - offset;
            }
            str = str.replaceAll(",", ";");
            str = str.replaceAll("\'", "\"");
            str = str.replaceAll("/", "");
        } catch (Exception e) {
            log4.error("error in replace: " + e);
        }
        return str;
    }
    
    /**
     * Modifies records before access to database
     * @param str
     * @return
     */
    private String replaceBefore(String str) {
        try {
            if (str.indexOf('"') != -1) {
                str = str.replaceAll("\"", "/\"");
            }
            if (str.indexOf(';') != -1) {
                str = str.replaceAll(";", "/;");
            }
        }
        catch (Exception e) {
            log4.error("error in replaceBefore: " + e);
        }
        return str;
    }

    /**
     * Creates database from file.
     * 
     * @return
     */
    private boolean create(Integer part) {

        String dbFile = new String();
        try {
            dbFile = nameDataBase + part.toString();
            BufferedReader br = new BufferedReader(new InputStreamReader(
                  new FileInputStream(dbFile)));
            String line;
            while ((line = br.readLine()) != null) {
                dBase.add(line);
            }
            br.close();
        } catch (FileNotFoundException e) {
            log4.error("create: File not found " + dbFile);
        } catch (IOException e) {
            log4.error("iostream error in create " + e);
        }
        return true;
    }

    /**
     * Destroys database. Writes new data in file.
     */
    private void destroy(Integer part) {

        try {
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
                new FileOutputStream(nameDataBase + part.toString(), false)));
            String line;
            for (String str : dBase) {
                line = new String(str);
                writer.write(line);
                writer.newLine();
            }
            writer.close();
        } catch (Exception e) {
            log4.error("error in destroy: " + e);
        }

    }

    /**
     * Selects needed records. Returns array of strings.
     * 
     * @param name
     * @return
     */
    public String[] select(String name) {

        Integer part = Partitioning(name);
        create(part);
        name = replaceBefore(name);
        ArrayList<Integer> index = findAll(name);
        String[] node = new String[index.size()];
        try {

            if (index.isEmpty()) {
                log4.info("select: item not found: " + name);
                return null;
            }
            for (Integer i = 0; i < index.size(); i++) {
                node[i] = dBase.get(index.get(i));
                node[i] = replace(node[i]);
            }

        } catch (Exception e) {
            log4.error("error in select: " + e);
        }
        destroy(part);
        return node;
    }

    /**
     * Inserts records in database without order (adds at end of database).
     * 
     * @param insertStr
     * @return
     */
    public boolean insert(String[] insertStr) {

        String name = insertStr[0];
        Integer part = Partitioning(name);
        create(part);
        boolean done = false;
        try {
            name = replaceBefore(name);
            String num = insertStr[1];
            String record = new String("\"" + name + "\"" + ";" + "\"" + num
                + "\"" + ";");
            done = dBase.add(record);
            undoLog.addToUndo("delete " + name);
        } catch (Exception e) {
            log4.error("error in insert: " + e);
            return false;
        }
        destroy(part);
        return done;
    }

    /**
     * Updates all records in database which username equals old name. Replaces old
     * records by new name and new number phone.
     * 
     * @param req
     * @return
     */
    public boolean update(String[] req) {

        String oldStr = req[0];
        Integer part = Partitioning(oldStr);
        create(part);
        try {
            for (int i =0; i < req.length; i++) {
                req[i] = replaceBefore(req[i]);
            }
            oldStr = req[0];
            ArrayList<Integer> index = findAll(oldStr);
            if (index.isEmpty()) {
                log4.info("update: item not found: " + oldStr);
                return false;
            }
            String newName = req[1];
            String newNum = req[2];
            String record = new String("\"" + newName + "\"" + ";" + "\""
                + newNum + "\"" + ";");
            String nums = new String();
            destroy(part);
            int newPart = Partitioning(req[1]);
            dBase.clear();
            create(newPart);
            for (int i = 0; i < index.size(); i++) {
                dBase.add(record);
            }
            destroy(newPart);
            dBase.clear();
            create(part);
            for (int i  = (index.size() - 1); i >= 0 ; i--) {
                int updateIndex = index.get(i);
                nums = getByIndex(updateIndex);
                dBase.remove(updateIndex);
                undoLog.addToUndo("update " + newName + "\r\n" + nums);
            }
            destroy(part);
        } catch (Exception e) {
            log4.error("error in update: " + e);
            return false;
        }
        
        return true;
    }

    /**
     * Updates record, which equals requested old record
     * @param req
     * @return
     */
    public boolean updateOne(String[] req) {
        
        try {
            String name = new String(req[0]);
            int nameIndex = name.lastIndexOf(" ");
            String oldName = name.substring(0, nameIndex);
            Integer part = Partitioning(oldName);
            create(part);
            String newName = req[1];
            String newNum = req[2];
            newName = replaceBefore(newName);
            newNum = replaceBefore(newNum);
            name = replaceBefore(name);
            int index = find(name);
            if (index != dBase.size()) {
                String record = new String("\"" + newName + "\"" + ";" + "\""
                    + newNum + "\"" + ";");
                String nums = getByIndex(index);
                dBase.remove(index);
                destroy(part);
                int newPart = Partitioning(req[1]);
                dBase.clear();
                create(newPart);
                dBase.add(record);
                destroy(newPart);
                undoLog.addToUndo("update one " + newName + newNum + "\r\n" + nums);
            }
            else {
                log4.info("updateOne: item not found " + req[0]);
                return false;
            }
            
        }
        catch(Exception e) {
            log4.error("error in updateOne: " + e);
        }
        return true;
    }
    /**
     * Deletes all records, which username equals @param delStr.
     * 
     * @param delStr
     * @return
     */
    public boolean delete(String delStr) {

        Integer part = Partitioning(delStr);
        create(part);
        delStr = replaceBefore(delStr);
        ArrayList<Integer> index = findAll(delStr);
        try {
            if (index.isEmpty()) {
                log4.info("delete: item not found " + delStr);
                return false;
            }
            String nums = new String();
            for (int i  = (index.size() - 1); i >= 0 ; i--) {
                int removeIndex = index.get(i);
                nums = getByIndex(removeIndex);
                dBase.remove(removeIndex);
                undoLog.addToUndo("insert " + nums);
            }

        } catch (Exception e) {
            log4.error("error in delete: " + e);
            return false;
        }
        destroy(part);
        return true;
    }
    
    /**
     * Deletes record, which equuals @param record
     * @param record
     * @return
     */
    public boolean deleteOne(String record) {
        
        try {
            String name = new String(record);
            int nameIndex = name.lastIndexOf(" ");
            name = name.substring(0, nameIndex);
            Integer part = Partitioning(name);
            create(part);
            name = replaceBefore(name);
            int index = find(record);
            if (index != dBase.size()) {
                dBase.remove(index);
                undoLog.addToUndo("insert " + record);
            }
            else {
                log4.info("deleteOne: item not found " + record);
                return false;
            }
            destroy(part);
        }
        catch(Exception e) {
            log4.error("error in deleteOne: " + e);
            return false;
        }
        return true;
    }
    
    /**
     * Returns record, which placed by index in database
     * @param index
     * @return
     */
    private String getByIndex(Integer index) {
        String record = new String();
        try {
            //journ.AddToJournal("select by index " + index);O
            if (index < 0 || index > dBase.size()) {
                log4.warn("incorrect index " + index);
                return null;
            }
            else {
                record = dBase.get(index);
                record = replace(record);
            }
        }
        catch (Exception e) {
            log4.error("error in select by index: " + e);
        }
        return record;
    }
    /**
     * Call private method getByIndex
     * @param index
     * @return
     */
    public String selectByIndex(Integer index) {
        
        create(0);
        String record = getByIndex(index);
        destroy(0);
        return record;
    }
    
    /**
     * Deletes record, which index equals @param index.
     * @param index
     * @return
     */
    public boolean deleteByIndex(Integer index) {
        
        create(0);
        try {
            //journ.AddToJournal("delete by index " + index);
            if (index < 0 || index > dBase.size()) {
                log4.warn("incorrect index " + index);
                return false;
            }
            else {
                dBase.remove(index);
            }
        }
        catch(Exception e) {
            log4.error("error in delete by index: " + e);
            return false;
        }
        destroy(0);
        return true;
    }
    
    public Integer Partitioning(String key) {
        
        Integer keyInt = 0;
        try {
            keyInt = key.hashCode();
            keyInt = Math.abs(keyInt % 4);
        }
        catch(Exception e) {
            log4.error("error in partitioning: " + e);
        }
        return keyInt;
    }
}